package com.yueyang.sojourn.server.common.repeatable;

import com.yueyang.sojourn.server.common.baymax.web.result.JAssert;
import com.yueyang.sojourn.server.config.AppUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Created by liguohui on 2019/4/24.
 */
@Component
@Aspect
@Slf4j
public class NotRepeatSupport {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String REPEAT_TAG_VALUE = "1";

    /**
     * 重复提交超时时间，默认为10分钟，最小单位为秒
     */
    private static final Long EXPIRED_TIME = 5 * 60L ;
    private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();


    /**
     * 前置通知
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut="@annotation(repeatable)")
    public  void doAfterReturning(JoinPoint joinPoint, NotRepeatable repeatable) {
        //处理结束以后，需要移除重复标记
        clearRepeatFlag(joinPoint,repeatable);
    }

    @AfterThrowing(value="@annotation(repeatable)",throwing="e")
    public void doThrowAfter(JoinPoint joinPoint, NotRepeatable repeatable, Exception e) {
        log.warn(e.getMessage(),e);
        //异常移除重复标记
        clearRepeatFlag(joinPoint,repeatable);
    }

    private void clearRepeatFlag(JoinPoint joinPoint, NotRepeatable repeatable) {
        String key = getKey(joinPoint, repeatable);
        redisTemplate.delete(key);
    }

    @Around("@annotation(repeatable)")
    public Object around(ProceedingJoinPoint joinPoint, NotRepeatable repeatable) throws Throwable{
        log.info("重复提交校验开始");
        boolean r = repeat(joinPoint,repeatable);
        JAssert.validParam(!r,"重复提交");
        return joinPoint.proceed(joinPoint.getArgs());
    }

    private boolean repeat(JoinPoint point, NotRepeatable repeatable){
        String key = getKey(point,repeatable);
        boolean result = redisTemplate.opsForValue().setIfAbsent(key,REPEAT_TAG_VALUE);
        if(!result){
            log.info("repeat submit: " + key);
            return true;
        }
        redisTemplate.expire(key,EXPIRED_TIME, TimeUnit.SECONDS);
        return false;
    }

    private String getKey(JoinPoint joinPoint, NotRepeatable repeatable) {
        // 拦截参数
        Object[] args = joinPoint.getArgs();
        // 拦截签名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method method = methodSignature.getMethod();
        // 拦截方法名
        String methodName = signature.getName();
        // 拦截实体类
        Object target = joinPoint.getTarget();
        // 拦截的类名
        Class clazz = target != null ? target.getClass() : Object.class;
        String keyEl = repeatable.key();
        Long ucid = AppUtil.getUcid();
        if (ucid == null) {
            ucid = -1L;
        }
        String key = clazz.getCanonicalName() + "." + methodName + "-" + ucid;
        if(StringUtils.isNotEmpty(keyEl)){
            String customerKey = genKey(keyEl,target,method,args);
            if(StringUtils.isNotEmpty(customerKey)){
                key = key + ":" + customerKey;
            }
        }
        return key;
    }


    private String genKey(String keySpel,Object target, Method method, Object[] params){
        keySpel = keySpel.trim();
        if(keySpel.startsWith("'") && keySpel.endsWith("'")){
            keySpel = keySpel.substring(1,keySpel.length()-1);
            return keySpel;
        }
        Map<String, Object> context = buildingParams(target,method,params);
        return SpelExpressionParserUtil.parser(keySpel, context);
    }

    private Map<String,Object> buildingParams(Object target, Method method, Object[] params) {
        String[] names = getParameterNames(method,params);
        Map<String,Object> contexts = new HashMap<>();
        for(int i = 0 ; i < names.length ; i++){
            if(names[i] != null ){
                contexts.put(names[i],params[i]);
            }
        }
        return contexts;
    }

    private String[] getParameterNames(Method method, Object[] params) {
        if (method.isBridge())
            method = BridgeMethodResolver.findBridgedMethod(method);
        Annotation[][] annotations =  method.getParameterAnnotations() ;
        String[] names = new String[annotations.length];
        boolean allbind = true;
        for (int i = 0; i < annotations.length; i++) {
            allbind = false;
        }
        if (!allbind) {
            String[] namesDiscovered = parameterNameDiscoverer.getParameterNames(method) ;
            if (namesDiscovered == null)
                return null;
            for (int i = 0; i < names.length; i++)
                if (names[i] == null)
                    names[i] = namesDiscovered[i];
        }
        return names;
    }
}
